#include "simulator.h"
#include "cache.h"
#include "log.h"
#include "cache_set_rrip_pacman.h"

// Cache class
// constructors/destructors
Cache::Cache(
   String name,
   String cfgname,
   core_id_t core_id,
   UInt32 num_sets,
   UInt32 associativity,
   UInt32 cache_block_size,
   String replacement_policy,
   cache_t cache_type,
   hash_t hash,
   FaultInjector *fault_injector,
   AddressHomeLookup *ahl)
:
   CacheBase(name, num_sets, associativity, cache_block_size, hash, ahl),
   m_enabled(false),
   m_num_accesses(0),
   m_num_hits(0),
   m_cache_type(cache_type),
   m_fault_injector(fault_injector),
   cache_pacman(Sim()->getCfg()->getBoolDefault(cfgname + "/pacman_dyn", false))
{
   m_set_info = CacheSet::createCacheSetInfo(name, cfgname, core_id, replacement_policy, m_associativity);
   m_sets = new CacheSet*[m_num_sets];

   if (cache_pacman) {
     const int nLeaders = 32;
     for (set_dueling_mask = 1; set_dueling_mask*nLeaders <= m_num_sets; set_dueling_mask <<= 1)
       set_dueling_bits ++;
     set_dueling_mask >>= 1;
     set_dueling_bits --;

     for (UInt32 i = 0; i < m_num_sets; i++) {
       m_sets[i] = CacheSet::createCacheSet
	 (cfgname, core_id, "rrip_pacman", m_cache_type, m_associativity, m_blocksize, m_set_info);
       CacheSetRRIP_PACMAN* _set = dynamic_cast<CacheSetRRIP_PACMAN*>(m_sets[i]);
       if (isSDMSet(i)) {
	 UInt32 sdm = SDMSet(i);
	 std::cout << "SDM " << sdm << std::endl;
	 switch (sdm % 3) {
	 case 0: /* SRRIP+PACMan-H */
	   _set->set_brrip(false);
	   _set->set_pacman_h(true);
	   _set->set_pacman_m(false);
	   break;
	 case 1: /* SRRIP+PACMan-HM */
	   _set->set_brrip(false);
	   _set->set_pacman_h(true);
	   _set->set_pacman_m(true);
	   break;
	 case 2: /* BRRIP+PACMan-H */
	   _set->set_brrip(true);
	   _set->set_pacman_h(true);
	   _set->set_pacman_m(false);
	 }
       }
     }
   } else {
     for (UInt32 i = 0; i < m_num_sets; i++) {
       m_sets[i] = CacheSet::createCacheSet(cfgname, core_id, replacement_policy, m_cache_type, m_associativity, m_blocksize, m_set_info);
     }
   }

   #ifdef ENABLE_SET_USAGE_HIST
   m_set_usage_hist = new UInt64[m_num_sets];
   for (UInt32 i = 0; i < m_num_sets; i++)
      m_set_usage_hist[i] = 0;
   #endif
}

Cache::~Cache()
{
   #ifdef ENABLE_SET_USAGE_HIST
   printf("Cache %s set usage:", m_name.c_str());
   for (SInt32 i = 0; i < (SInt32) m_num_sets; i++)
      printf(" %" PRId64, m_set_usage_hist[i]);
   printf("\n");
   delete [] m_set_usage_hist;
   #endif

   if (m_set_info)
      delete m_set_info;

   for (SInt32 i = 0; i < (SInt32) m_num_sets; i++)
      delete m_sets[i];
   delete [] m_sets;
}

Lock&
Cache::getSetLock(IntPtr addr)
{
   IntPtr tag;
   UInt32 set_index;

   splitAddress(addr, tag, set_index);
   assert(set_index < m_num_sets);

   return m_sets[set_index]->getLock();
}

bool
Cache::invalidateSingleLine(IntPtr addr)
{
   IntPtr tag;
   UInt32 set_index;

   splitAddress(addr, tag, set_index);
   assert(set_index < m_num_sets);

   return m_sets[set_index]->invalidate(tag);
}

CacheBlockInfo*
Cache::accessSingleLine(IntPtr addr, access_t access_type,
			Byte* buff, UInt32 bytes,
			SubsecondTime now,
			bool update_replacement,
			bool isprefetch)
{
   //assert((buff == NULL) == (bytes == 0));

   IntPtr tag;
   UInt32 set_index;
   UInt32 line_index = -1;
   UInt32 block_offset;

   splitAddress(addr, tag, set_index, block_offset);

   CacheSet* set = m_sets[set_index];
   CacheBlockInfo* cache_block_info = set->find(tag, &line_index);

   if (cache_block_info == NULL)
      return NULL;

   if (access_type == LOAD)
   {
      // NOTE: assumes error occurs in memory. If we want to model bus errors, insert the error into buff instead
      if (m_fault_injector)
         m_fault_injector->preRead(addr, set_index * m_associativity + line_index, bytes, (Byte*)m_sets[set_index]->getDataPtr(line_index, block_offset), now);

      set->read_line(line_index, block_offset, buff, bytes, update_replacement, isprefetch);
   }
   else
   {
     set->write_line(line_index, block_offset, buff, bytes, update_replacement, isprefetch);

      // NOTE: assumes error occurs in memory. If we want to model bus errors, insert the error into buff instead
      if (m_fault_injector)
         m_fault_injector->postWrite(addr, set_index * m_associativity + line_index, bytes, (Byte*)m_sets[set_index]->getDataPtr(line_index, block_offset), now);
   }

   return cache_block_info;
}

void
Cache::insertSingleLine(IntPtr addr, Byte* fill_buff,
			bool* eviction, IntPtr* evict_addr,
			CacheBlockInfo* evict_block_info,
			Byte* evict_buff,
			SubsecondTime now,
			CacheCntlr *cntlr,
			bool isprefetch)
{
   IntPtr tag;
   UInt32 set_index;
   splitAddress(addr, tag, set_index);

   CacheBlockInfo* cache_block_info = CacheBlockInfo::create(m_cache_type);
   cache_block_info->setTag(tag);

   if (cache_pacman) {
     // leader set

     // follower set
     
   }
   
   m_sets[set_index]->insert(cache_block_info, fill_buff,
			     eviction, evict_block_info,
			     evict_buff, cntlr, isprefetch);
   *evict_addr = tagToAddress(evict_block_info->getTag());

   if (m_fault_injector) {
      // NOTE: no callback is generated for read of evicted data
      UInt32 line_index = -1;
      __attribute__((unused)) CacheBlockInfo* res = m_sets[set_index]->find(tag, &line_index);
      LOG_ASSERT_ERROR(res != NULL, "Inserted line no longer there?");

      m_fault_injector->postWrite(addr, set_index * m_associativity + line_index, m_sets[set_index]->getBlockSize(), (Byte*)m_sets[set_index]->getDataPtr(line_index, 0), now);
   }

   #ifdef ENABLE_SET_USAGE_HIST
   ++m_set_usage_hist[set_index];
   #endif

   delete cache_block_info;
}


// Single line cache access at addr
CacheBlockInfo*
Cache::peekSingleLine(IntPtr addr)
{
   IntPtr tag;
   UInt32 set_index;
   splitAddress(addr, tag, set_index);

   return m_sets[set_index]->find(tag);
}

void
Cache::updateCounters(bool cache_hit)
{
   if (m_enabled)
   {
      m_num_accesses ++;
      if (cache_hit)
         m_num_hits ++;
   }
}

void
Cache::updateHits(Core::mem_op_t mem_op_type, UInt64 hits)
{
   if (m_enabled)
   {
      m_num_accesses += hits;
      m_num_hits += hits;
   }
}

int
Cache::getMinSDM()
{
  int idx = 0;
  for (int i=1; i<nsdm; i++) {
    if (sdm[i]<sdm[idx])
      idx = i;
  }
  return idx;
}
